package com.libservice.eventtracking

import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.libbase.utils.JsonUtils
import com.libbase.utils.genericType
import com.libservice.ServiceManager
import com.libservice.eventtracking.services.EventTrackingAPI
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Date
import java.util.UUID
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

class EventTracking : EventTrackingInterface {
    companion object {
        const val TAG: String = "EventTracking"

        private const val MAX_UPLOAD_LOGS_COUNT: Int = 10
    }

    private val eventTrackingExecutor: ExecutorService = Executors.newSingleThreadExecutor()

    private var disposable: Disposable? = null

    private val logs: MutableList<Map<String, Any>> = ServiceManager.instance.cacheService
        .globalCache
        .getObject<MutableList<Map<String, Any>>>(
            "logsData", genericType<MutableList<Map<String, Any>>>()
        ) ?: mutableListOf()

    private var uploadingLogsCount = ServiceManager.instance.cacheService
        .globalCache
        .getInt("uploadingLogsCount") ?: 0

    override fun initEventTracking() {
        addLifecycleObserver()
    }

    override fun tracking(event: String, params: Map<String, Any>?) {
        val data: MutableMap<String, Any> = mutableMapOf(
            "event" to event,
            "uuid" to UUID.randomUUID().toString(),
            "timestamp" to Date().time
        )

        params?.let { data["params"] = it }

        @OptIn(DelicateCoroutinesApi::class)
        GlobalScope.launch(eventTrackingExecutor.asCoroutineDispatcher()) {
            logs.add(data)
            saveLogs()

            upload()
        }
    }

    private fun saveLogs() {
        ServiceManager.instance.cacheService
            .globalCache.setObject("logsData", logs)
    }

    private fun removeUploadedLogs() {
        logs.subList(0, uploadingLogsCount).clear()
        setUploadLogs(0)
        saveLogs()
    }

    private fun setUploadLogs(count: Int) {
        uploadingLogsCount = count

        ServiceManager.instance.cacheService
            .globalCache.setInt("uploadingLogsCount", uploadingLogsCount)
    }

    private fun upload() {
        if (disposable != null) {
            return
        }

        setUploadLogs(if (logs.count() < MAX_UPLOAD_LOGS_COUNT) logs.count() else MAX_UPLOAD_LOGS_COUNT)

        val checkUpload = {
            disposable = null

            if (uploadingLogsCount == 0 && logs.isNotEmpty()) {
                upload()
            }
        }

        disposable = EventTrackingAPI
            .log(
                JsonUtils.json.toJson(logs.take(uploadingLogsCount))
                    .toRequestBody("application/json".toMediaTypeOrNull())
            )
            .observeOn(Schedulers.from(eventTrackingExecutor))
            .subscribe({ result ->
                if (!result.isSuccess()) {
                    Log.e(TAG, result.message ?: "upload error")

                    checkUpload()
                    return@subscribe
                }

                removeUploadedLogs()

                checkUpload()
            }, {
                it.printStackTrace()

                checkUpload()
            })
    }

    private fun addLifecycleObserver() {
        ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onCreate(owner: LifecycleOwner) {
                tracking("appCreate", null)
            }

            override fun onStart(owner: LifecycleOwner) {
                tracking("appStart", null)
            }

            override fun onStop(owner: LifecycleOwner) {
                tracking("appStop", null)
            }

            override fun onDestroy(owner: LifecycleOwner) {
                tracking("appDestroy", null)
            }
        })
    }
}